/** @file log.h
	@brief Logging system */

#ifndef G3_LOG_H
#define G3_LOG_H

#include "GLee/GLee.h"

#include <GL/gl.h>
#include <GL/glu.h>

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>

#include <string>

#include "version.h"
#include "text.h"

/** 5 KB of maximum log printf buffer */
#define LOG_BUFFER      5120

/** A macro that adds current source code filename, function name and line as arguments to a function */
#define FILE_LINE       __FILE__, __FUNCTION__, __LINE__
/** A macro that concatenates current source code filename and line into a string */
#define FILE_LINE_STR   (std::string (__FILE__) + ":" + std::string (__FUNCTION__) + ":" + G3::IntegerToStr (__LINE__))

/** Generic engine */
namespace G3 {

/** The maximum log buffer size */
extern long Default_Log_Sizelimit;

/** @class LOG
    @brief The logging system class */
class LOG {
    public:
        /**
            @brief Constructor that opens the file "engine.log" for overwriting or appending
            @param[in] logSizeLimit The maximal log file size.
            The log file is overwritten if its size exceeds this limit.
        */
        LOG (long logSizeLimit = Default_Log_Sizelimit);

        /**
            @brief Constructor that opens a file for logging
            @param[in] filename Path to the log file to be overwritten or appended
            @param[in] logSizeLimit The maximal log file size.
            The log file is overwritten if its size exceeds this limit.
        */
        LOG (const char *filename, long logSizeLimit = Default_Log_Sizelimit);

        /**
            @brief It's a destructor
        */
        ~LOG ();

        /**
            @brief Is there a logfile open? Is there any memory allocated for this class?
            @return True if there is and false if not
        */
        bool IsOk ();

        /**
            @brief Reports something to the log
            @param[in] fmt The somethin' to be reported
        */
        void Report (const char *fmt, ...);

    private:
        /** Is a new header written every time the log is opened ? */
        bool WriteHeaderOnOpen;
        /** Should the header contain date ? */
        bool WriteDateOnReport;
        /** What's the sizelimit for this log ? */
        long LogSizeLimit;
        /** Pointer to the logfile structure */
        FILE *File;

        /**
            @brief Opens a logfile for appending if the file is not larger than the sizelimit.
            If it is, the log file is replaced with a new one
            @param[in] filename Path to the logfile output
            @return True on success, false on failure
        */
        bool Open (const char *filename);

        /**
            @brief Writes a header (version, log update time) into the logfile
        */
        void WriteHeader ();

        /**
            @brief Writes the date (log update time) into a logfile
        */
        void WriteDate ();

        /**
            @brief Closes the logfile
        */
        void Close ();
};

/** The main log ("engine.log") */
extern LOG MainLog;

class LOGGABLE {
    public:
        LOGGABLE () {
            log = &MainLog;
        }

        virtual ~LOGGABLE () {
            log = NULL;
        }

        LOG *log;

        virtual void dump () {}
};

/** @class EX
    @brief An exception class */
class EX {
    public:
        /**
            @brief Constructor
            @param[in] desc Description of the exception (what happened ?)
            @param[in] val The value to be passed along with the description
        */
        EX (const char *desc, const char *file, const char *func, int line) {
            description = std::string (desc);
            src_file = std::string (file);
            src_line = line;
            src_func = std::string (func);
        }

        /** The description */
        std::string description;
        /** The source file, from where the exception was thrown */
        std::string src_file;
        /** The source function, from where the exception was thrown */
        std::string src_func;
        /** The line from which the exception was thrown */
        int src_line;

        /**
            @brief Converts the exception into a string
            @return The string
        **/
        std::string toString () {
            return src_file + ":" + src_func + ":" + IntegerToStr (src_line) + ": " + description;
        }
};

/** @class EX_STR
    @brief An exception object with an additional string */
class EX_STR : public EX {
    public:
        /**
            @brief Constructor
            @param[in] desc Description of the exception (what happened ?)
            @param[in] val The value to be passed along with the description
        */
        EX_STR (const char *desc, const std::string &val, const char *file, const char *func, int line) :
        EX (desc, file, func, line) {
            str = val;
        }

        /** The additional string */
        std::string str;

        /**
            @brief Converts the exception into a string
            @return The string
        **/
        std::string toString () {
            return ((EX *) this)->toString () + " " + str;
        }
};

/** @class EX_INT
    @brief An exception object with an additional integer variable */
class EX_INT : public EX {
    public:
        /**
            @brief Constructor
            @param[in] desc Description of the exception (what happened ?)
            @param[in] val The value to be passed along with the description
        */
        EX_INT (const char *desc, int val, const char *file, const char *func, int line) :
        EX (desc, file, func, line) {
            value = val;
        }

        /** The additional integer */
        int value;

        /**
            @brief Converts the exception into a string
            @return The string
        **/
        std::string toString () {
            return ((EX *) this)->toString () + " " + IntegerToStr (value);
        }
};

/**
    @brief An assert function
    @param[in] aExpression The expression that has to be true. If it's false, things have gone wrong..
    @param[in] aDescription Description of the event - what has happened if the expression turns out to be false ?
    @param[in] aLine The problem occurred on which line ?
    @param[in] aFile The file in which the problem showed up
    @param[in] aIgnoreAlways Should the assertion be ignored in the future ?
    @return True if a SIGTRAP should be fired, false otherwise
*/
bool AssertFunc (bool aExpression, const char *aDescription, int aLine, const char *aFile, bool *aIgnoreAlways);

/**
    @brief Performs an OpenGL error check
    @param[in] log Pointer to the log
    @param[in] glfunc Reference to the OpenGL function name to check
    @param[in] file The file in which the error check should be performed
    @param[in] func In which function should it be performed
    @param[in] line At which line might the error have occurred ?
*/
bool glErrCheck (LOG *log, const std::string &glfunc, const char *file, const char *func, int line);

}

/** An assert macro that breaks into the debugger on failure
    and that also supports the AlwaysIgnore option */
#ifdef _DEBUG
#define Assert(exp, desc) 					 	 \
	{ static bool AlwIgn = false; 				 \
	  if (!AlwIgn) {							 \
	  	if (G3::AssertFunc (exp, desc, __LINE__, \
						__FILE__, &AlwIgn)) {	 \
			printf ("Assert returned true\n");   \
			__asm ("int $3\n");					 \
		}										 \
	  }											 \
	}
#else
#define Assert(exp, desc)
#endif

#endif //G3_LOG_H
